﻿using System;
using VisionarySystems.TimeRegi.Model;
using VisionarySystems.TimeRegi.View.WorkTimeCalculator;

namespace VisionarySystems.TimeRegi.View
{
	public class SummaryGenerator
	{
		#region private members
		private readonly IWorkTimeCalculator m_WorkTimeCalculator;
		private readonly ICurrentTimeHandler m_CurrentTimeHandler;
		private readonly BreakTimeCalculator m_BreakTimeCalculator;

		#endregion

		#region Ctor

		public SummaryGenerator(IWorkTimeCalculator WorkTimeCalculator, ICurrentTimeHandler CurrentTimeHandler, BreakTimeCalculator BreakTimeCalculator)
		{
			m_WorkTimeCalculator = WorkTimeCalculator;
			m_CurrentTimeHandler = CurrentTimeHandler;
			m_BreakTimeCalculator = BreakTimeCalculator;
		}

		#endregion

		#region public methods

		/// <summary>
		/// Will generate a sting telling:
		/// - Worktime current day
		/// - Expected worktime this week
		/// - Overtime 
		/// - Closing time
		/// </summary>
		/// <returns></returns>
		public string Generate()
		{
			TimeSpan overTime = GetOverTime();
			string overTimeStr = "xx:xx";
			if ((overTime < new TimeSpan(999, 59, 00)) && (overTime > new TimeSpan(-999, -59, 00)))
				overTimeStr = TimeSpanToStr(overTime);
			return string.Format("Idag:{0}, Denne uge:{1}, Overtid:{2}, Fyraften:{3}",
								 TimeSpanToStr(GetWorkedToday()),
								 TimeSpanToStr(GetThisWeeksExpectedWorkTime()),
								 overTimeStr,
								 DateTimeToStr(GetClosingTime()));
		}

		/// <summary>
		/// Get the calculated closing time.
		/// The closing time, will be calculated to work 37 hours for the week
		/// </summary>
		/// <returns></returns>
		public DateTime GetClosingTime()
		{
			TimeSpan thisWeek = GetThisWeeksExpectedWorkTime();
			TimeSpan expectedOvertime = thisWeek.Subtract(new TimeSpan(37, 0, 0));

			TimeSpan workedToDay = GetWorkedToday();
			TimeSpan expectedWorkToday = DaysOffCalculator.ExpectedWorkHoursPerDay(m_CurrentTimeHandler.StartTime.Date.DayOfWeek).Add(m_BreakTimeCalculator.ExpectedBreakTimePerDay(m_CurrentTimeHandler.StartTime.Date.DayOfWeek));
			//TimeSpan breakTimeToday = GetBreakTime();
			TimeSpan overTimeToday = workedToDay.Subtract(expectedWorkToday);
			//DateTime result = m_CurrentTimeHandler.CurrentTime.Subtract(overTimeToday).Subtract(expectedOvertime);
			DateTime result = m_CurrentTimeHandler.StartTime.Add(workedToDay).Subtract(overTimeToday).Subtract(expectedOvertime);
			return result;

		}

		/// <summary>
		/// Returns the time worked over
		/// </summary>
		/// <returns></returns>
		public TimeSpan GetOverTime()
		{
			return m_WorkTimeCalculator.GetOverTime();
		}

		/// <summary>
		/// Returns the hours expected work this week
		/// based on the hours worked since monday this week
		/// and expected normal working hours the rest of the week
		/// If work hours the currentday is > normal working hours, these will be added
		/// </summary>
		/// <returns></returns>
		public TimeSpan GetThisWeeksExpectedWorkTime()
		{
			TimeSpan result = new TimeSpan();
			try
			{
				DateTime firstDayOfWeek = GetFirstDayOfWeek(m_CurrentTimeHandler.StartTime);
				DayOfWeek currentDayOfWeek = m_CurrentTimeHandler.StartTime.DayOfWeek;
				result = m_WorkTimeCalculator.GetTime(firstDayOfWeek, m_CurrentTimeHandler.StartTime.Date.AddSeconds(-1), true); // This week, not today

				result = result.Add(GetExpectedWorkToday());

				TimeSpan expectedWorkRestOfWeek = new TimeSpan();
				DateTime day = m_CurrentTimeHandler.StartTime.Date.AddDays(1);
				while (day.DayOfWeek != DayOfWeek.Monday)
				{
					expectedWorkRestOfWeek = expectedWorkRestOfWeek.Add(DaysOffCalculator.ExpectedWorkHoursPerDay(day.DayOfWeek));
					day = day.AddDays(1);
				}
				result = result.Add(expectedWorkRestOfWeek);
			}
			catch (Exception)
			{
				//day.AddDAys fails at midhnight. I dont understand why...
			}
			return result;
		}

		/// <summary>
		/// Returns the normal worktime for today, unless current worktime is higher
		/// </summary>
		/// <returns></returns>
		private TimeSpan GetExpectedWorkToday()
		{
			TimeSpan result;
			TimeSpan workedToDay = GetWorkedToday();
			TimeSpan expectedWorkToday = DaysOffCalculator.ExpectedWorkHoursPerDay(m_CurrentTimeHandler.StartTime.Date.DayOfWeek);
			result = workedToDay > expectedWorkToday ? workedToDay : expectedWorkToday;
			return result;
		}

		public TimeSpan GetWorkedToday()
		{
			TimeSpan timeSlotsForToday = GetTimeSlotsForToday();

			TimeSpan workedThisSession = GetWorkedThisSession();

			TimeSpan breakTime = GetBreakTime();

			return timeSlotsForToday.Add(workedThisSession).Subtract(breakTime);
		}

		private TimeSpan GetTimeSlotsForToday()
		{
			return m_WorkTimeCalculator.GetTime(m_CurrentTimeHandler.StartTime.Date, m_CurrentTimeHandler.StartTime, false, false);
		}

		private TimeSpan GetWorkedThisSession()
		{
			return m_CurrentTimeHandler.CurrentTime.Subtract(m_CurrentTimeHandler.StartTime);
		}

		private TimeSpan GetBreakTime()
		{
			TimeSpan breakTime;
			if (IsBreakStarted())
			{
				if (IsBreakEnded())
					breakTime = m_BreakTimeCalculator.BreakPerDay;
				else
					breakTime = m_CurrentTimeHandler.CurrentTime - GetBreakStart();
			}
			else
				breakTime = new TimeSpan();
			return breakTime;
		}

		private DateTime GetBreakStart()
		{
			return m_CurrentTimeHandler.CurrentDate.AddHours(12);
		}

		private bool IsBreakEnded()
		{
			return m_CurrentTimeHandler.CurrentTime > GetBreakStart().Add(m_BreakTimeCalculator.BreakPerDay);
		}

		private bool IsBreakStarted()
		{
			return m_CurrentTimeHandler.CurrentTime > GetBreakStart();
		}

		#endregion

		#region private methods

		private DateTime GetFirstDayOfWeek(DateTime StartTime)
		{
			StartTime = StartTime.Date;
			int delta = DayOfWeek.Monday - StartTime.DayOfWeek;
			if (delta > 0)
				delta -= 7;
			return StartTime.AddDays(delta);
		}


		private string DateTimeToStr(DateTime Time)
		{
			return String.Format("{0:D2}:{1:D2}", Time.Hour, Time.Minute);
		}

		private string TimeSpanToStr(TimeSpan span)
		{
			return String.Format("{0:D2}:{1:D2}", (int)span.TotalHours, span.Minutes);
		}

		#endregion

	}
}